﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Server
{
    public class QueueManager
    {
        /// <summary>
        /// This value is add if user computed another part project.
        /// </summary>
        const byte BONUS_VALUE = 10;

        /// <summary>
        /// The connection to DB.
        /// </summary>
        private ServerDataBaseContainer _connection;

        /// <summary>
        /// Initializes a new instance of the <see cref="QueueManager" /> class.
        /// </summary>
        /// <param name="connection">The connection.</param>
        public QueueManager(ServerDataBaseContainer connection)
        {
            _connection = connection;
        }

        /// <summary>
        /// Gets the favorite project id to next compiuting.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <returns></returns>
        public int GetFavoriteProjectId(Guid userId, int skip)
        {
            IQueryable<Project> projects = _connection
                .ProjectSet
                .Where(p => p.Status.Id == (int)ProjestStatuses.Active);

            List<int> userProjectsIds = GetProjectsUsedByUser(userId);

            int proritySum = projects
                .Sum(p => (int?)p.Priorytet) ?? 0;
            proritySum += userProjectsIds.Count * BONUS_VALUE;

            int activeCompiutingCount = _connection.
                CompiutingSet.Count(c => c.CompiutingStatus.Id == (int)CompiutingStatuses.InProgress);


            var queue = projects.ToList()
                .Select(p => new
                {
                    Id = p.Id,
                    FillPriorytet = FillPriorytet(userProjectsIds, proritySum, p),
                    Fulfillment = Fulfillment(activeCompiutingCount, p)
                });
            int projectId;
            try
            {
                projectId = queue.
                    OrderByDescending(q => q.FillPriorytet)
                    .Where(q => q.Fulfillment <= q.FillPriorytet)
                    .Skip(skip)
                    .First()
                    .Id;
            }
            catch (Exception ex)
            {
                throw new Exception("No avible projects", ex);
            }


            return projectId;
        }


        /// <summary>
        /// Fills the priorytet.
        /// </summary>
        /// <param name="userProjectsIds">The projects ids using by user.</param>
        /// <param name="proritySum">The prority sum.</param>
        /// <param name="project">The project.</param>
        /// <returns></returns>
        private static float FillPriorytet(List<int> userProjectsIds, int proritySum, Project project)
        {
            float priorytet = project.Priorytet + (userProjectsIds.Exists(i => i == project.Id) ? BONUS_VALUE : 0);
            return priorytet / proritySum;
        }

        /// <summary>
        /// Fulfillments the specified project.
        /// </summary>
        /// <param name="activeCompiutingCount">The active compiuting count for all projects.</param>
        /// <param name="project">The project.</param>
        /// <returns>Fulfillment.</returns>
        private static float Fulfillment(int activeCompiutingCount, Project project)
        {
            if (activeCompiutingCount == 0)
            {
                return 0;
            }
            else
            {
                return (float)project.Compiuting.Count(c => c.CompiutingStatus.Id == 1) / activeCompiutingCount;
            }
        }


        /// <summary>
        /// Gets the projects used by user.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <returns>List project id</returns>
        private List<int> GetProjectsUsedByUser(Guid userId)
        {
            List<int> userProjectsIds = _connection
                .CompiutingUnitsSet
                .First(u => u.Guid == userId)
                .Compiuting
                .Where(c => c.Project.Status.Id == (int)ProjestStatuses.Active)
                .Select(c => c.Project.Id)
                .ToList();
            return userProjectsIds;
        }
    }
}
